/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 *    PrecomputedKernelMatrixKernel.java
 *    Copyright (C) 2008 University of Waikato, Hamilton, New Zealand
 *
 */
package weka.classifiers.functions.supportVector;

import weka.core.Option;
import weka.core.matrix.Matrix;
import weka.core.Instances;
import weka.core.Utils;
import weka.core.Capabilities;
import weka.core.Capabilities.Capability;
import weka.core.Instance;
import weka.core.RevisionHandler;
import weka.core.RevisionUtils;

import java.util.Enumeration;
import java.util.Vector;

import java.io.FileReader;
import java.io.File;

/**
 * 
 <!-- globalinfo-start --> This kernel is based on a static kernel matrix that
 * is read from a file. Instances must have a single nominal attribute
 * (excluding the class). This attribute must be the first attribute in the file
 * and its values are used to reference rows/columns in the kernel matrix. The
 * second attribute must be the class attribute.
 * <p/>
 * <!-- globalinfo-end -->
 * 
 * <!-- options-start --> Valid options are:
 * <p/>
 * 
 * <pre>
 * -D
 *  Enables debugging output (if available) to be printed.
 *  (default: off)
 * </pre>
 * 
 * <pre>
 * -no-checks
 *  Turns off all checks - use with caution!
 *  (default: checks on)
 * </pre>
 * 
 * <pre>
 * -M &lt;file name&gt;
 *  The file name of the file that holds the kernel matrix.
 *  (default: kernelMatrix.matrix)
 * </pre>
 * 
 * <!-- options-end -->
 * 
 * @author Eibe Frank (eibe@cs.waikato.ac.nz)
 * @version $Revision: 5518 $
 */
public class PrecomputedKernelMatrixKernel extends Kernel {

	/** for serialization */
	static final long serialVersionUID = -321831645846363333L;

	/** The file holding the kernel matrix. */
	protected File m_KernelMatrixFile = new File("kernelMatrix.matrix");

	/** The kernel matrix. */
	protected Matrix m_KernelMatrix;

	/** A classifier counter. */
	protected int m_Counter;

	/**
	 * Returns a string describing the kernel
	 * 
	 * @return a description suitable for displaying in the
	 *         explorer/experimenter gui
	 */
	public String globalInfo() {
		return "This kernel is based on a static kernel matrix that is read from a file. "
				+ "Instances must have a single nominal attribute (excluding the class). "
				+ "This attribute must be the first attribute in the file and its values are "
				+ "used to reference rows/columns in the kernel matrix. The second attribute "
				+ "must be the class attribute.";
	}

	/**
	 * Returns an enumeration describing the available options.
	 * 
	 * @return an enumeration of all the available options.
	 */
	public Enumeration listOptions() {
		Vector result;

		result = new Vector();

		Enumeration en = super.listOptions();
		while (en.hasMoreElements())
			result.addElement(en.nextElement());

		result.addElement(new Option(
				"\tThe file name of the file that holds the kernel matrix.\n"
						+ "\t(default: kernelMatrix.matrix)", "M", 1,
				"-M <file name>"));

		return result.elements();
	}

	/**
	 * Parses a given list of options.
	 * <p/>
	 * 
	 * <!-- options-start --> Valid options are:
	 * <p/>
	 * 
	 * <pre>
	 * -D
	 *  Enables debugging output (if available) to be printed.
	 *  (default: off)
	 * </pre>
	 * 
	 * <pre>
	 * -no-checks
	 *  Turns off all checks - use with caution!
	 *  (default: checks on)
	 * </pre>
	 * 
	 * <pre>
	 * -M &lt;file name&gt;
	 *  The file name of the file that holds the kernel matrix.
	 *  (default: kernelMatrix.matrix)
	 * </pre>
	 * 
	 * <!-- options-end -->
	 * 
	 * @param options
	 *            the list of options as an array of strings
	 * @throws Exception
	 *             if an option is not supported
	 */
	public void setOptions(String[] options) throws Exception {
		String tmpStr;

		tmpStr = Utils.getOption('M', options);
		if (tmpStr.length() != 0)
			setKernelMatrixFile(new File(tmpStr));
		else
			setKernelMatrixFile(new File("kernelMatrix.matrix"));

		super.setOptions(options);
	}

	/**
	 * Gets the current settings of the Kernel.
	 * 
	 * @return an array of strings suitable for passing to setOptions
	 */
	public String[] getOptions() {
		int i;
		Vector<String> result;
		String[] options;

		result = new Vector<String>();

		options = super.getOptions();
		for (i = 0; i < options.length; i++)
			result.add(options[i]);

		result.add("-M");
		result.add("" + getKernelMatrixFile());

		return (String[]) result.toArray(new String[result.size()]);
	}

	/**
	 * 
	 * @param id1
	 *            the index of instance 1
	 * @param id2
	 *            the index of instance 2
	 * @param inst1
	 *            the instance 1 object
	 * @return the dot product
	 * @throws Exception
	 *             if something goes wrong
	 */
	public double eval(int id1, int id2, Instance inst1) throws Exception {

		if (m_KernelMatrix == null) {
			throw new IllegalArgumentException(
					"Kernel matrix has not been loaded successfully.");
		}
		int index1 = -1;
		if (id1 > -1) {
			index1 = (int) m_data.instance(id1).value(0);
		} else {
			index1 = (int) inst1.value(0);
		}
		int index2 = (int) m_data.instance(id2).value(0);
		return m_KernelMatrix.get(index1, index2);
	}

	/**
	 * initializes variables etc.
	 * 
	 * @param data
	 *            the data to use
	 */
	protected void initVars(Instances data) {
		super.initVars(data);

		try {
			if (m_KernelMatrix == null) {
				m_KernelMatrix = new Matrix(new FileReader(m_KernelMatrixFile));
				// System.err.println("Read kernel matrix.");
			}
		} catch (Exception e) {
			System.err.println("Problem reading matrix from "
					+ m_KernelMatrixFile);
		}
		m_Counter++;
		// System.err.print("Building classifier: " + m_Counter + "\r");
	}

	/**
	 * Returns the Capabilities of this kernel.
	 * 
	 * @return the capabilities of this object
	 * @see Capabilities
	 */
	public Capabilities getCapabilities() {
		Capabilities result = super.getCapabilities();
		result.disableAll();

		result.enable(Capability.NOMINAL_ATTRIBUTES);
		result.enableAllClasses();
		result.enable(Capability.MISSING_CLASS_VALUES);

		return result;
	}

	/**
	 * Sets the file holding the kernel matrix
	 * 
	 * @param value
	 *            the exponent value
	 */
	public void setKernelMatrixFile(File f) {
		m_KernelMatrixFile = f;
	}

	/**
	 * Gets the file containing the kernel matrix.
	 * 
	 * @return the exponent value
	 */
	public File getKernelMatrixFile() {
		return m_KernelMatrixFile;
	}

	/**
	 * Returns the tip text for this property
	 * 
	 * @return tip text for this property suitable for displaying in the
	 *         explorer/experimenter gui
	 */
	public String kernelMatrixFileTipText() {
		return "The file holding the kernel matrix.";
	}

	/**
	 * Set the kernel matrix. This method is used by the unit test for this
	 * class, as it loads at test matrix as a system resource.
	 * 
	 * @param km
	 *            the kernel matrix to use
	 */
	protected void setKernelMatrix(Matrix km) {
		m_KernelMatrix = km;
	}

	/**
	 * returns a string representation for the Kernel
	 * 
	 * @return a string representaiton of the kernel
	 */
	public String toString() {
		return "Using kernel matrix from file with name: "
				+ getKernelMatrixFile();
	}

	/**
	 * Frees the memory used by the kernel. (Useful with kernels which use
	 * cache.) This function is called when the training is done. i.e. after
	 * that, eval will be called with id1 == -1.
	 */
	public void clean() {
		// do nothing
	}

	/**
	 * Returns the number of kernel evaluation performed.
	 * 
	 * @return the number of kernel evaluation performed.
	 */
	public int numEvals() {
		return 0;
	}

	/**
	 * Returns the number of dot product cache hits.
	 * 
	 * @return the number of dot product cache hits, or -1 if not supported by
	 *         this kernel.
	 */
	public int numCacheHits() {
		return 0;
	}

	/**
	 * Returns the revision string.
	 * 
	 * @return the revision
	 */
	public String getRevision() {
		return RevisionUtils.extract("$Revision: 5518 $");
	}
}
